package ua.lviv.franko.mlsFunctions.oneDimension;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import ua.lviv.franko.integration.IFunction1D;
import ua.lviv.franko.matrix.SimpleLU;
import ua.lviv.franko.mlsFunctions.oneDimension.weightFunctions.Gaussian;
import ua.lviv.franko.mlsFunctions.oneDimension.weightFunctions.IWeightFunction;
import ua.lviv.franko.solvers.oneDimention.HasBaseNode;
import ua.lviv.franko.visualisation.NewOkCancelDialog;

public class BaseFunction implements HasBaseNode, IFunction1D {

	protected int				PolinomialDegree;
	protected Particle			xi;
	protected IWeightFunction	func;
	protected double[][]		A;
	protected double[]			b;
	protected List<Particle>	nodeList;
	protected List<Double>		baseVector;

	protected double			EPS	= 0.0000001;

	public BaseFunction(int PolinomialDegree, Particle xi, IWeightFunction func) {
		this.PolinomialDegree = PolinomialDegree;
		this.xi = xi;
		this.func = func;
		baseVector = getBaseVector(xi.xi, xi);
	}

	public double calculate(double x) {
		if ((xi.xi - xi.rho > x) || (xi.xi + xi.rho < x)) {
			return 0;
		}

		int n = baseVector.size();
		b = new double[n];
		// формуємо матрицю Грамма М
		A = new double[n][n];
		for (int i = 0; i < nodeList.size(); i++) {
			Particle particle = nodeList.get(i);
			calculateAlfa(x, particle);
		}

		double val = func.calculate(x, xi.xi, xi.rho);
		boolean key = false;
		for (int i = 0; i < b.length; i++) {
			b[i] = baseVector.get(i) * val;
			if (Math.abs(b[i]) > EPS) {
				key = true;
			}
		}

		double returnValue = 0.0;
		if (key) {
			SimpleLU lu = new SimpleLU(A);
			double[] res = lu.calculate(b);

			List<Double> localBaseVector = getBaseVector(x, new Particle(x, 0.0));
			for (int i = 0; i < b.length; i++) {
				returnValue += localBaseVector.get(i) * res[i];
			}
		}
		if (Double.isNaN(returnValue)) {
			return 0;
		}
		return returnValue;
	}

	public Values[] calculateAllFunctions(double x) {
		int n = baseVector.size();
		double[][] bb = new double[n][nodeList.size()];
		// формуємо матрицю Грамма М
		A = new double[n][n];
		for (int i = 0; i < nodeList.size(); i++) {
			Particle particle = nodeList.get(i);
			calculateAlfa(x, particle);
		}

		double val = func.calculate(x, xi.xi, xi.rho);
		// BaseVector = getBaseVector(x, new Particle(x, 0.0));
		// boolean key=true;
		for (int i = 0; i < bb[0].length; i++) {
			Particle p = nodeList.get(i);
			List<Double> localBaseVector = getBaseVector(p.xi, p);
			val = func.calculate(x, p.xi, p.rho);
			for (int j = 0; j < bb.length; j++) {
				bb[j][i] = localBaseVector.get(j) * val;
			}
		}

		Values[] returnValue = new Values[nodeList.size()];
		SimpleLU lu = new SimpleLU(A);
		double[][] alpha = lu.calculate(bb);

		List<Double> localBaseVectorDerivaty = getBaseVectorDerivaty(x, new Particle(x, 0.0));
		for (int particleNumber = 0; particleNumber < bb[0].length; particleNumber++) {
			Particle particle = nodeList.get(particleNumber);
			double[] tmp = new double[bb.length];
			for (int j = 0; j < this.nodeList.size(); j++) { // по вузлах
				Particle xj = nodeList.get(j);
				val = func.calculateDerivaty(x, xj.xi, xj.rho);// func.evaluateDerivaty((x-xj.x)/xj.rho);
				List<Double> localBaseVector = this.getBaseVector(xj.xi, xj);
				double tmpVal = 0.0;
				for (int k = 0; k < alpha.length; k++) {// альфа на вектор Р і вагу ...
					tmpVal += localBaseVector.get(k) * alpha[k][particleNumber];
				}

				for (int k = 0; k < alpha.length; k++) {// число на вектор Р
					tmp[k] += localBaseVector.get(k) * tmpVal * val;
				}
			}
			val = func.calculateDerivaty(x, particle.xi, particle.rho);
			double val2 = func.calculate(x, particle.xi, particle.rho);
			for (int i = 0; i < bb.length; i++) {
				bb[i][particleNumber] = baseVector.get(i) * val - tmp[i];
			}
		}

		double[][] alphaPrime = lu.calculate(bb);

		List<Double> localBaseVector = getBaseVector(x, new Particle(x, 0.0));
		localBaseVectorDerivaty = getBaseVectorDerivaty(x, new Particle(x, 0.0));
		for (int particleNumber = 0; particleNumber < bb[0].length; particleNumber++) {
			double valF = 0.0;
			double valFx = 0.0;
			Particle p = nodeList.get(particleNumber);
			if ((p.xi - p.rho > x) || (p.xi + p.rho < x)) {
				returnValue[particleNumber] = new Values(valF, valFx, x);
				continue;
			}
			for (int j = 0; j < bb.length; j++) {
				valF += localBaseVector.get(j) * alpha[j][particleNumber];
				valFx += localBaseVectorDerivaty.get(j) * alpha[j][particleNumber] + localBaseVector.get(j) * alphaPrime[j][particleNumber];
				// valFx += localBaseVectorDerivaty.get(j) * alpha[j][particleNumber];
			}
			returnValue[particleNumber] = new Values(valF, valFx, x);
		}

		return returnValue;
	}

	public double[] calculateAllFunctionsValuesOnly(double x) {
		int n = baseVector.size();
		double[][] bb = new double[n][nodeList.size()];
		// формуємо матрицю Грамма М
		A = new double[n][n];
		for (int i = 0; i < nodeList.size(); i++) {
			Particle particle = nodeList.get(i);
			calculateAlfa(x, particle);
		}

		double val = func.calculate(x, xi.xi, xi.rho);
		// BaseVector = getBaseVector(x, new Particle(x, 0.0));
		// boolean key=true;
		for (int i = 0; i < bb[0].length; i++) {
			Particle p = nodeList.get(i);
			List<Double> localBaseVector = getBaseVector(p.xi, p);
			val = func.calculate(x, p.xi, p.rho);
			for (int j = 0; j < bb.length; j++) {
				bb[j][i] = localBaseVector.get(j) * val;
			}
		}

		double[] returnValue = new double[nodeList.size()];
		SimpleLU lu = new SimpleLU(A);
		double[][] res = lu.calculate(bb);

		List<Double> localBaseVector = getBaseVector(x, new Particle(x, 0.0));
		for (int i = 0; i < bb[0].length; i++) {
			for (int j = 0; j < bb.length; j++) {
				returnValue[i] += localBaseVector.get(j) * res[j][i];
			}
		}

		return returnValue;
	}

	public double calculateDerivaty(double x) {
		int n = baseVector.size();
		b = new double[n];
		// формуємо матрицю Грамма М
		A = new double[n][n];
		for (int i = 0; i < nodeList.size(); i++) {
			Particle particle = nodeList.get(i);
			calculateAlfa(x, particle);
		}

		double val = func.calculate(x, xi.xi, xi.rho);
		// BaseVector = getBaseVector(x, new Particle(x, 0.0));
		boolean key = false;
		for (int i = 0; i < b.length; i++) {
			b[i] = baseVector.get(i) * val;
			if (Math.abs(b[i]) > EPS) {
				key = true;
			}
		}

		double returnValue = 0.0;
		if (key) {
			SimpleLU lu = new SimpleLU(A);
			double[] res = lu.calculate(b);

			key = false;
			double[] tmp = new double[b.length];
			for (int j = 0; j < this.nodeList.size(); j++) { // по вузлах
				Particle xj = nodeList.get(j);
				val = func.calculateDerivaty(x, xj.xi, xj.rho);// func.evaluateDerivaty((x-xj.x)/xj.rho);
				List<Double> localBaseVector = this.getBaseVector(xj.xi, xj);
				double tmpVal = 0.0;
				for (int k = 0; k < res.length; k++) {// альфа на вектор Р і вагу ...
					tmpVal += localBaseVector.get(k) * res[k];
				}

				for (int k = 0; k < res.length; k++) {// число на вектор Р
					tmp[k] += localBaseVector.get(k) * tmpVal * val;
				}
			}
			val = func.calculateDerivaty(x, xi.xi, xi.rho); // func.evaluateDerivaty((x-xii)/rho);
			// BaseVector = this.getBaseVector(xi.xi, xi);
			for (int i = 0; i < b.length; i++) {
				b[i] = baseVector.get(i) * val - tmp[i];
				if (Math.abs(b[i]) > EPS) {
					key = true;
				}
			}
			List<Double> localBaseVectorDerivaty = this.getBaseVectorDerivaty(x, new Particle(x, 0.0));
			for (int j = 0; j < res.length; j++) {
				returnValue += localBaseVectorDerivaty.get(j) * res[j];
			}
			if (key) {
				double[] res2 = lu.calculate(b);

				List<Double> baseVectorLocal = this.getBaseVector(x, new Particle(x, 0.0));
				for (int j = 0; j < res2.length; j++) {
					returnValue += baseVectorLocal.get(j) * res2[j];
				}
			}

		}
		if (Double.isNaN(returnValue)) {
			return 0;
		}
		return returnValue;

	}

	public double calculateDerivatyAproximation(double x) {
		if ((xi.xi - xi.rho > x) || (xi.xi + xi.rho < x)) {
			return 0;
		}
		int n = baseVector.size();
		b = new double[n];
		// формуємо матрицю Грамма М
		A = new double[n][n];
		for (int i = 0; i < nodeList.size(); i++) {
			Particle particle = nodeList.get(i);
			calculateAlfa(x, particle);
		}

		double val = func.calculate(x, xi.xi, xi.rho);
		// BaseVector = getBaseVector(x, new Particle(x, 0.0));
		boolean key = false;
		for (int i = 0; i < b.length; i++) {
			b[i] = baseVector.get(i) * val;
			if (Math.abs(b[i]) > EPS) {
				key = true;
			}
		}

		double returnValue = 0.0;
		if (key) {
			SimpleLU lu = new SimpleLU(A);
			double[] res = lu.calculate(b);

			// BaseVector = getBaseVector(xi.xi, xi);
			List<Double> baseVectorLocal = getBaseVectorDerivaty(x, new Particle(x, 0.0));
			for (int i = 0; i < b.length; i++) {
				returnValue += baseVectorLocal.get(i) * res[i];
			}
		}
		if (Double.isNaN(returnValue)) {
			return 0;
		}
		return returnValue;
	}

	public ArrayList<Double> getBaseVector(double x, Particle xi) {
		ArrayList<Double> L = new ArrayList<Double>();
		for (int i = 0; i <= this.PolinomialDegree; i++) {
			L.add(Math.pow((xi.xi), i));
		}
		// L.add(Math.exp(xi.xi));
		return L;
	}

	public ArrayList<Double> getBaseVectorDerivaty(double x, Particle xi) {
		ArrayList<Double> L = new ArrayList<Double>();
		L.add(0.0);
		for (int i = 1; i <= this.PolinomialDegree; i++) {
			L.add(i * Math.pow((xi.xi), i - 1));
		}
		// L.add(Math.exp(xi.xi));
		return L;
	}

	public ArrayList<Double> getBaseVectorSecondDerivaty(double x, Particle xi) {
		ArrayList<Double> L = new ArrayList<Double>();
		L.add(0.0);
		L.add(0.0);
		for (int i = 2; i <= this.PolinomialDegree; i++) {
			L.add(i * (i - 1) * Math.pow((xi.xi), i - 2));
		}
		return L;
	}

	public List<Particle> getNodeList() {
		return this.nodeList;
	}

	public Particle getXi() {
		return this.xi;
	}

	public void setNodeList(List<Particle> nodeList) {
		this.nodeList = nodeList;
	}

	private void calculateAlfa(double x, Particle xi) {
		ArrayList<Double> BaseVector = getBaseVector(x, xi);
		int n = BaseVector.size();
		double funcVal = func.calculate(x, xi.xi, xi.rho);
		for (int i = 0; i < n; i++) {
			double val = BaseVector.get(i) * funcVal;
			for (int j = 0; j < n; j++) {
				A[i][j] += val * BaseVector.get(j);
			}
		}
	}

	public static void main(String[] args) {

		double a = 0.0, b = 1;
		int nn = 10;
		ArrayList<Particle> ls1D = new ArrayList<Particle>();
		int polinomDegree = 1;
		double h = (b - a) / (double) nn;
		double rho = 2.1 * h;
		// // rho = 0.5;
		for (int i = 0; i <= nn; i++) {
			ls1D.add(new Particle(a + h * i, rho));
		}

		XYSeriesCollection col = new XYSeriesCollection();

		// for (int i = 0; i < ls1D.size(); i++) {
		int i = ls1D.size() / 2;
		Particle particle = ls1D.get(i);

		BaseFunction jmf1 = new BaseFunction(polinomDegree, particle, new Gaussian());
		jmf1.setNodeList(ls1D);

		// XYSeries s = new XYSeries("calculateDerivaty", true, true);
		// double xxx = a;
		// while (xxx <= b + 0.0001) {
		// double tmp1 = jmf1.calculateDerivaty(xxx);
		// s.add(xxx, tmp1);
		// xxx += 0.002;
		// }
		// col.addSeries(s);
		//
		// s = new XYSeries("calculateDerivatyAproximation", true, true);
		// xxx = a;
		// while (xxx <= b + 0.0001) {
		// double tmp1 = jmf1.calculateDerivatyAproximation(xxx);
		// s.add(xxx, tmp1);
		// xxx += 0.002;
		// }
		// col.addSeries(s);
		//
		// s = new XYSeries("calculateAllFunctionsGauss(xxx)[i].valDerivaty", true, true);
		// xxx = a;
		// while (xxx <= b + 0.0001) {
		// double tmp1 = jmf1.calculateAllFunctions(xxx)[i].valDerivaty;
		// s.add(xxx, tmp1);
		// xxx += 0.002;
		// }
		// col.addSeries(s);
		IWeightFunction weightFunction = new Gaussian();
		BaseFunction function = new BaseFunction(polinomDegree, particle, weightFunction);
		function.setNodeList(getParticleSupport(ls1D, particle, weightFunction));

		long start = System.nanoTime();
		function.calculateAllFunctions(particle.xi);
		System.out.println("calculateAllFunctions - " + (System.nanoTime() - start));

		start = System.nanoTime();
		function.calculate(particle.xi);
		System.out.println("calculate - " + (System.nanoTime() - start));

		start = System.nanoTime();
		function.calculateDerivaty(particle.xi);
		System.out.println("calculateAllFunctions - " + (System.nanoTime() - start));

		for (int j = 0; j < function.getNodeList().size(); j++) {
			XYSeries ser = new XYSeries("[i].valDerivaty", true, true);
			double xxxxxxx = a;
			while (xxxxxxx <= b + 0.0001) {
				double tmp1 = function.calculateAllFunctions(xxxxxxx)[j].valDerivaty;
				ser.add(xxxxxxx, tmp1);
				xxxxxxx += 0.002;
			}
			col.addSeries(ser);
		}

		NewOkCancelDialog d = new NewOkCancelDialog(null, false, col);
		d.setLocationRelativeTo(null);
		d.setVisible(true);

	}

	private static List<Particle> getParticleSupport(List<Particle> particles, Particle particle, IWeightFunction weight) {
		List<Particle> list = new ArrayList<Particle>();
		for (Iterator<Particle> iterator = particles.iterator(); iterator.hasNext();) {
			Particle p = (Particle) iterator.next();
			if (weight.intersectedSupport(particle, p)) {
				list.add(p);
			}
		}
		return list;
		// return particles;
	}

}